﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGinnBPM.Runtime;
using NGinnBPM.Lib.Data;
using NGinnBPM.Lib.Schema;
using NGinnBPM.Lib.Interfaces;
using NGinnBPM.Runtime.Events;
using NGinnBPM.Lib.Exceptions;
using System.Runtime.Serialization;
using NLog;
using NGinnBPM.MessageBus;
using NGinnBPM.Runtime.Messages;

namespace NGinnBPM.Runtime.Tasks
{
    [Serializable]
    [DataContract]
    public class SubprocessTaskInstance : AtomicTaskInstance,
        ITaskMessageHandler<TaskSelected>,
        ITaskMessageHandler<TaskCompleted>,
        ITaskMessageHandler<TaskFailed>,
        ITaskMessageHandler<TaskCancelled>,
        ITaskMessageHandler<TaskInstanceTimeout>,
        ITaskMessageHandler<StartSubprocessMessageResponse>
    {
        private string _subprocessInstance;
        private string _definitionId;

        /// <summary>
        /// Subprocess instance ID, assigned after 
        /// it starts.
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput=false)]
        public string SubprocesssInstance
        {
            get { return _subprocessInstance; }
            set { _subprocessInstance = value; }
        }

        /// <summary>
        /// Set async to true if you don't want to 
        /// wait for the subprocess completion.
        /// This task will then complete as soon as the 
        /// subprocess is started.
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput=true)]
        public bool Async { get; set; }


        /// <summary>
        /// Subprocess definition ID
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput=true, Required=true, DynamicAllowed=true)]
        public string SubprocessDefinition
        {
            get { return _definitionId; }
            set { _definitionId = value; }
        }

        /// <summary>
        /// Endpoint of remote NGinn message bus
        /// where the subprocess should be started.
        /// If you don't set it the process will be
        /// started locally.
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput=true, Required=false, DynamicAllowed=true)]
        public string RemoteEngineEndpoint { get; set; }

        public override void Enable(Dictionary<string, object> inputData)
        {
            base.Enable(inputData);
            if (RemoteEngineEndpoint != null && RemoteEngineEndpoint.Length > 0)
            {
                Context.MessageBus.NewMessage(new StartSubprocessMessage
                {
                    DefinitionId = this.SubprocessDefinition,
                    ParentTaskInstanceId = this.InstanceId,
                    InputData = TaskData
                })
                .SetCorrelationId(this.InstanceId)
                .Send(RemoteEngineEndpoint);
                ScheduleInternalTimeout(DateTime.Now.AddHours(12), this.InstanceId);
                return; //stay enabled until a response arrives
            }
            else
            {
                //start the subprocess synchronously
                SubprocesssInstance = Context.Environment.StartSubprocess(SubprocessDefinition, TaskData, InstanceId);
                if (Async)
                {
                    Complete(null, null);
                }
            }
        }

        

        public override void Cancel()
        {
            lock (this)
            {
                if (Status == TaskStatus.Cancelled)
                {
                    log.Info("Task already cancelled.");
                    return;
                }
                if (Status != TaskStatus.Enabled && Status != TaskStatus.Selected)
                    throw new InvalidTaskStatusException(InstanceId, "invalid status").SetTaskId(TaskId);
                Context.Environment.CancelProcessInstance(_subprocessInstance);
                Status = TaskStatus.Cancelled;
                DefaultHandleTaskCancelled();
            }
        }



        #region IMessageConsumer<TaskSelected> Members

        public void Handle(TaskSelected message)
        {
            log.Info("Child process started");
            if (message.ParentTaskInstanceId != InstanceId)
                throw new TaskRuntimeException("Invalid parent task instance ID").SetInstanceId(InstanceId);
            if (message.SourceTaskInstanceId != SubprocesssInstance)
                throw new TaskRuntimeException("Invalid child subprocess ID").SetInstanceId(InstanceId);
            Select();
        }

        #endregion

        #region IMessageConsumer<TaskCompleted> Members

        public void Handle(TaskCompleted message)
        {
            log.Info("Child process completed");
            if (message.ParentTaskInstanceId != InstanceId)
                throw new TaskRuntimeException("Invalid parent task instance ID").SetInstanceId(InstanceId);
            if (message.SourceTaskInstanceId != SubprocesssInstance)
                throw new TaskRuntimeException("Invalid child subprocess ID").SetInstanceId(InstanceId);

            if (Status == TaskStatus.Completed)
                return;
            if (Status != TaskStatus.Enabled &&
                Status != TaskStatus.Selected)
                throw new InvalidTaskStatusException(InstanceId, "Invalid status").SetTaskId(TaskId);
            Complete(message.CompletedBy, message.OutputData);
        }

        #endregion

        #region IMessageConsumer<TaskFailed> Members

        public void Handle(TaskFailed message)
        {
            log.Info("Child process failed");
            if (message.ParentTaskInstanceId != InstanceId)
                throw new TaskRuntimeException("Invalid parent task instance ID").SetInstanceId(InstanceId);
            if (message.SourceTaskInstanceId != SubprocesssInstance)
                throw new TaskRuntimeException("Invalid child subprocess ID").SetInstanceId(InstanceId);

            if (Status == TaskStatus.Failed)
                return;
            if (Status != TaskStatus.Enabled &&
                Status != TaskStatus.Selected)
                throw new InvalidTaskStatusException(InstanceId, "Invalid status").SetTaskId(TaskId);
            DefaultHandleTaskFailure(message.ErrorInfo, message.IsExpected);
        }

        #endregion

        #region IMessageConsumer<TaskCancelled> Members

        public void Handle(TaskCancelled message)
        {
            log.Info("Child process cancelled");
            if (message.ParentTaskInstanceId != InstanceId)
                throw new TaskRuntimeException("Invalid parent task instance ID").SetInstanceId(InstanceId);
            if (message.SourceTaskInstanceId != SubprocesssInstance)
                throw new TaskRuntimeException("Invalid child subprocess ID").SetInstanceId(InstanceId);

            if (Status == TaskStatus.Cancelled)
                return;
            if (Status != TaskStatus.Enabled &&
                Status != TaskStatus.Selected)
                throw new InvalidTaskStatusException(InstanceId, "Invalid status").SetTaskId(TaskId);
            Status = TaskStatus.Cancelled;
            DefaultHandleTaskCancelled();
        }

        #endregion

        #region IMessageConsumer<TaskInstanceTimeout> Members

        public void Handle(TaskInstanceTimeout message)
        {
            if (this.Status == TaskStatus.Enabled)
            {
                Fail("Timeout");
            }
        }

        #endregion

        #region IMessageConsumer<StartSubprocessMessageResponse> Members

        public void Handle(StartSubprocessMessageResponse message)
        {
            if (Status == TaskStatus.Enabled)
            {
                if (message.Success)
                {
                    SubprocesssInstance = message.SubprocessInstanceId;
                    if (Async)
                    {
                        Complete(null, null);
                    }
                    else
                    {
                        Select();
                    }
                }
                else
                {
                    Fail(message.ErrorInfo);
                }
            }
            else
            {
                throw new NotImplementedException();
            }
        }

        #endregion
    }
}
